//============================================================================
// Name        : fast-dbrd.cpp
// Author      : Chengnian SUN
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <cassert>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <iostream>
#include <numeric>
#include <vector>

using namespace std;

#include "util/CmdOption.h"
#include "detector/AbstractDuplicateDetector.h"
#include "extensions/IDetectorExtension.h"
#include "extensions/OnlineResultCollector.h"
#include "extensions/RecommendationRecorder.h"
#include "util/RandomUtility.h"
#include "util/MacroUtility.h"
#include "detector/DetectorFactory.h"
#include "report-model/ReportDataset.h"

//#define COUNT_TO_SKIP 200

using namespace std;

template<class T>
void print_average_result_statistics(FILE* file, const char* name,
                                     vector<vector<T> >& correct_list_list, const bool to_print_rank) {
    fprintf(file, "Average %s...\n", name);
    const unsigned number_of_iterations = correct_list_list.size();
    assert(number_of_iterations > 0);
    const unsigned number_of_top = correct_list_list[0].size();
    const unsigned denominator = number_of_iterations;
    for (unsigned rank = 0; rank < number_of_top; rank++) {
        T sum = 0;
        for (unsigned j = 0; j < number_of_iterations; j++) {
            sum += correct_list_list[j][rank];
        }
        if (to_print_rank) {
            fprintf(file, "TOP(%2u)=%f\n", (rank + 1),
                    (static_cast<double>(sum) / denominator));
        } else {
            fprintf(file, "%f\n", (static_cast<double>(sum) / denominator));
        }
    }
}

template<class T>
void print_result_statistics(FILE* file, const char* name,
                             const vector<T>& list, const unsigned number_of_total_duplicates,
                             const double denominator, const bool to_print_rank) {
    fprintf(file, "%s...\n", name);
    const unsigned size_of_top = list.size();

    for (unsigned i = 0; i < size_of_top; i++) {
        const T value = list[i];
        if (to_print_rank) {
            fprintf(file, "TOP(%2u)(/%u)=%f\n", (i + 1), number_of_total_duplicates,
                    value / denominator);
        } else {
            fprintf(file, "%f\n", value);
        }
    }
}

/**
 * 2021-2-23 shangchao添加：打印 dbrdXXXranknetXXX 文件的头部
 */
void print_file_header(FILE* file, const CmdOption& option) {
    time_t total_start_time = time(NULL);
    fprintf(file,
            "====================================================================\n");
    fprintf(file, "--Started at %s", ctime(&total_start_time));
    fprintf(file, "--Dataset file = %s\n", option.get_dataset_path().c_str());
    fprintf(file, "--Total Iterations = %u\n", option.get_iteration());
    fprintf(file,
            "====================================================================\n\n\n");
}

/**
 * 2021-2-23 打开命令行参数中指定的 recommend文件，用来保存本程序为每个报告生成的推荐列表
 * @param option
 * @return
 */
FILE* open_recommendation_file(const CmdOption& option) {
    const string name = option.get_recommendation_file();
    if (name.empty()) {
        return NULL;
    } else {
        FILE* file = fopen(name.c_str(), "w");
        if (file)
            return file;
        else {
            UNREACHABLE("Cannot open recommendation file!");
            return NULL;
        }
    }
}

/**
 * 打开 一个文件，取名为 dbrdXXXX，用来存放本程序的一些结果
 * 包括 训练得到的参数，推荐的结果（recall，map等）
 * @param option
 * @return
 */
FILE* open_result_file(const CmdOption& option) {
    char name[400];
    const char* project_name = option.get_project_name().c_str();
    int evolution = option.is_to_evolve_model();
    int iteration = option.get_iteration();
    // name = dbrd_<project>_F-<feature>_

    // 先判断是否使用 svm检测器
    if (option.is_using_svm_detector()) {
        const char* feature_name =
                FeatureVectorCalculatorFactory::get_enum_feature_calculator_type_string(
                        option.get_feature_calculator_type()).c_str();
        sprintf(name, "dbrd_%s_%s_F-%s_E-%s_I-%d", project_name, "svm",
                feature_name, (evolution ? "true" : "false"), iteration);
    } else if (option.is_using_plain_detector()) {
        const char* measure_name =
                PlainSimilarityMeasureFactory::get_similarity_measure_type_string(
                        option.get_plain_similarity_type()).c_str();
        sprintf(name, "dbrd_%s_%s_P-%s_I-%d", project_name, "plain", measure_name,
                iteration);
    } else if (option.is_using_ranknet_detector()) {
        sprintf(name, "dbrd_%s_%s_I-%d", project_name, "ranknet", iteration);
    } else {
        UNREACHABLE("unrecognized detector type!");
    }
    FILE* result = fopen(name, "w");
    if (result == NULL) {
        char message[200];
        sprintf(message, "cannot create output file %s\n", name);
        ERROR_HERE(message);
    }
    return result;
}

int main(int argc, char* argv[]) {
//  if (1)
//    return 0;
    CmdOption* option = new CmdOption(argc, argv);
    option->validate_options();
    if (option->has_parsing_error()) {
        option->print_parsing_error();
        return EXIT_FAILURE;
    } else if (option->has_help()) {
        option->print_help();
        return EXIT_SUCCESS;
    } else if (option->has_version()) {
        option->print_version();
        return EXIT_SUCCESS;
    }

    if (option->has_validation_error()) {
        option->print_validation_error();
        option->print_help();
        return EXIT_FAILURE;
    }

    vector<vector<double> > recall_list;
    vector<double> map_list;

    vector<time_t> times;
    const unsigned total_iterations = option->get_iteration();
    unsigned number_of_duplicates = 0;

    FILE* file = open_result_file(*option);                           // result_file 是 dbrdXXXranknetXXX
    FILE* recommendation_file = open_recommendation_file(*option);    //打开 --recommend指定的xxxResults文件
    print_file_header(file, *option);
    // 2021-2-22 shangchao 添加：在这里读取reports-large.txt文件，即读取各个字段的信息
    ReportDataset dataset(option->get_dataset_path().c_str());
    const int top_k = option->get_top_k();
    const double limitL = option->get_limit_k();
    const int trainingStartID=option->get_trainingStartId();          // 缺陷报告的起始ID
    cout << "trainingStartId = " << trainingStartID << endl;
    const int trainingEndID=option->get_trainingEndId();
    cout << "trainingEndId = " << trainingEndID << endl;

    // 通过-i来指定循环迭代的次数
    for (unsigned iteration = 0; iteration < total_iterations; iteration++) {

        fprintf(file, "Iteration %u\n", iteration + 1);
        fprintf(stdout, "Iteration %u\n", iteration + 1);
        if (recommendation_file)
            fprintf(recommendation_file, "Iteration %u\n", iteration + 1);

        // 记录下来开始计算之前的时间，用于最后测量计算使用了多长时间
        const time_t start_time = time(NULL);
        // 产生随机种子
        const unsigned seed = RandomUtility::get_default().re_seed();
        fprintf(file, "Random seed = %u\n", seed);


        vector<IDetectorExtension*> extentions;

        // top_k 是命令行参数 -k指定的
        // file是文件句柄，通过 open_result_file函数生成，名称为 dbrd_xxxx，存储的是每一个报告的检索结果
        OnlineResultCollector* collector = new OnlineResultCollector(top_k, file);
        extentions.push_back(collector);
        RecommendationRecorder* recorder = NULL;

        // 如果在命令行中通过参数 --recommend指定了recommend文件，那么算法会把每个报告的检索推荐列表都存储到recommend文件中
        if (recommendation_file) {
            RecommendationRecorder* recorder = new RecommendationRecorder(top_k,
                                                                          recommendation_file);
            extentions.push_back(recorder);
        }

        AbstractDuplicateDetector* detector = NULL;

        // 2021-2-24 shangchao 这里调用create_detector来生成rankNet模型，后面还有调参等步骤
        detector = DetectorFactory::create_detector(file, trainingStartID, trainingEndID, limitL, top_k,
                                                    extentions, *option, dataset);


        // 这里看起来是真正的进行算法检测重复缺陷报告的部分，先初始化detector，再调用detect函数进行检测
        // init 主要是创建 检索算法（比如RankNet）
        detector->init();
        detector->detect();

        // 上面的detector检测完了，才能计算整个检测过程的计算时间
        const time_t elapsed_time = time(NULL) - start_time;

        times.push_back(elapsed_time);
        fprintf(file, "\nElapsed time = %u\n", static_cast<unsigned>(elapsed_time));
        fprintf(stdout, "\nElapsed time = %u\n",
                static_cast<unsigned>(elapsed_time));

        // 判断重复缺陷报告的数量是否正确
        assert(
                number_of_duplicates == 0 || number_of_duplicates == collector->get_number_of_duplicates());
        number_of_duplicates = collector->get_number_of_duplicates();

        const vector<double>& recall = collector->get_recall_result();
        recall_list.push_back(recall);

        // 输出 从startId到endId之间的缺陷报告的 结果的统计情况
        print_result_statistics<double>(file, "Recall", recall,
                                        number_of_duplicates, 1, true);
        print_result_statistics<double>(file, "Recall", recall,
                                        number_of_duplicates, 1, false);

        const double map = collector->get_mean_average_precision();
        map_list.push_back(map);
        fprintf(file, "MAP = %f\n", map);

        detector->dispose();

        delete detector;
        delete collector;
        delete recorder;
        fprintf(file, "\n\n\n");
        fprintf(stdout, "\n\n\n");
        if (recommendation_file) {
            fprintf(recommendation_file, "\n\n\n");
            fflush(recommendation_file);
        }
        fflush(file);

    }

    delete option;
    option = NULL;

    // for循环结束之后，输出多次循环计算结果的平均值
    print_average_result_statistics<double>(file, "Average Recall", recall_list,
                                            true);

    print_average_result_statistics<double>(file, "Average Recall", recall_list,
                                            false);

    // 对map_list数据结构里的数据，从begin()到end()进行求和
    const double average_map = accumulate(map_list.begin(), map_list.end(), 0.0f)
                               / total_iterations;
    fprintf(file, "Average MAP = %f\n", average_map);
    if (recommendation_file)
        fclose(recommendation_file);
    fclose(file);

    RandomUtility::dispose_default();
    return EXIT_SUCCESS;
}
